/*
    symbol 用来对对象属性名或者字符串进行独一无二的定义
    避免和其他同名的方法或者属性名等产生冲突
    属于 JavaScript 语言的原生数据类型之一，
    其他数据类型是：
        undefined、null、布尔值（Boolean）、字符串（String）、数值（Number）、大整数（BigInt）、对象（Object）
    Symbol()函数前不能使用new命令，否则会报错。这是因为生成的 Symbol 是一个原始类型的值，不是对象，所以不能使用new命令来调用。
    另外，由于 Symbol 值不是对象，所以也不能添加属性。基本上，它是一种类似于字符串的数据类型。
    Symbol()函数可以接受一个字符串作为参数，表示对 Symbol 实例的描述。这主要是为了在控制台显示，或者转为字符串时，比较容易区分。
*/
let s = Symbol();
console.log(typeof s)
let s1 = Symbol('foo');
let s2 = Symbol('bar');

console.log(s1) // Symbol(foo)
console.log(s2) // Symbol(bar)

console.log(s1.toString())// "Symbol(foo)"
console.log(s2.toString()) // "Symbol(bar)"
// 如果 Symbol 的参数是一个对象，就会调用该对象的toString()方法，将其转为字符串，然后才生成一个 Symbol 值
const obj = {
    toString() {
        return 'abc';
    }
};
const sym = Symbol(obj);
console.log(sym)
// Object类型的toString()方法，用于返回一个表示该对象的字符串，
// 该字符串并不是将对象的所有键值对变成字符串，而是生成一个原始值，返回 "[object Type]"
const obj2 = {
    name: 'zs',
    age: 12
}
const sym2 = Symbol(obj2)
console.log(sym2)//Symbol([object Object])
// 重写toString方法
Object.prototype.toString = function () {
    // this指向调用该方法的对象
    // 返回对象的JSON字符串
    return JSON.stringify(this);
}

// 注意，Symbol()函数的参数只是表示对当前 Symbol 值的描述，因此相同参数的Symbol函数的返回值是不相等的。
// 没有参数的情况
let str1 = Symbol();
let str2 = Symbol();

console.log(str1 === str2) // false

// 有参数的情况
let str3 = Symbol('foo');
let str4 = Symbol('foo');

console.log(s1 === s2) // false

// Symbol 值不能与其他类型的值进行运算，会报错。
let sym3 = Symbol('My symbol');

// console.log("your symbol is " + sym3)// TypeError: can't convert symbol to string
// console.log(`your symbol is ${sym3}`)// TypeError: can't convert symbol to string

// 但是，Symbol 值可以显式转为字符串。
let sym4 = Symbol('My symbol');

console.log(String(sym4)) // 'Symbol(My symbol)'
console.log(sym.toString()) // 'Symbol(My symbol)'

// 另外，Symbol 值也可以转为布尔值，但是不能转为数值。
let sym5 = Symbol();
console.log(Boolean(sym5)) // true
console.log(!sym5)// false

if (sym5) {
    // ...
    console.log('sym5是Boolean值')
}

// console.log(Number(sym5)) // TypeError
// console.log(Number(sym + 2) // TypeError

// 注意，Symbol 值作为对象属性名时，不能用点运算符。
const mySymbol = Symbol();
const a = {};

a.mySymbol = 'Hello!';
// a[mySymbol] // undefined
console.log(a['mySymbol']) // "Hello!"
/*  
    上面代码中，因为点运算符后面总是字符串，
    所以不会读取mySymbol作为标识名所指代的那个值，
    导致a的属性名实际上是一个字符串，而不是一个 Symbol 值。
    使用 Symbol 值定义属性时，Symbol 值必须放在方括号之中。
 */

/*
    Symbol 值作为属性名，遍历对象的时候，该属性不会出现在for...in、for...of循环中，
    也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。
    但是，它也不是私有属性，有一个Object.getOwnPropertySymbols()方法，
    可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组，成员是当前对象的所有用作属性名的 Symbol 值。
    注意：常规的键名不会被返回
 */
console.log('-----------')
const obj4 = {};
let a2 = Symbol('a2');
let b2 = Symbol('b2');
obj4[a2] = 'Hello';
obj4[b2] = 'World';
obj4.c2 = '!!!'
const objectSymbols = Object.getOwnPropertySymbols(obj4);
console.log(objectSymbols)// [Symbol(a2), Symbol(b2)]


// 一个新的 API，Reflect.ownKeys()方法可以返回所有类型的键名，包括常规键名和 Symbol 键名
let objReflect = {
    [Symbol('my_key')]: 1,
    enum: 2,
    nonEnum: 3
};
console.log(Reflect.ownKeys(objReflect))//[ 'enum', 'nonEnum', Symbol(my_key) ]

/*
Symbol.for()方法
它接受一个字符串作为参数，然后搜索有没有以该参数作为名称的 Symbol 值。
如果有，就返回这个 Symbol 值，否则就新建一个以该字符串为名称的 Symbol 值，并将其注册到全局。
*/
let sfor1 = Symbol.for('foo');
let sfor2 = Symbol.for('foo');

console.log(sfor1 === sfor2)// true
/*
Symbol.for()与Symbol()对比：
    Symbol.for()与Symbol()这两种写法，都会生成新的 Symbol。它们的区别是，前者会被登记在全局环境中供搜索，后者不会。
    Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值，而是会先检查给定的key是否已经存在，如果不存在才会新建一个值。
*/

console.log('-----------')
// 由于Symbol()写法没有登记机制，所以每次调用都会返回一个不同的值。
// Symbol.keyFor()方法返回一个已登记的 Symbol 类型值的key。
Symbol.keyFor(sfor1)
let symbol = Symbol("foo");
console.log(Symbol.keyFor(symbol)) // undefined
// 注意，Symbol.for()为 Symbol 值登记的名字，是全局环境的，不管有没有在全局环境运行。

/*          Symbol.hasInstance
对象的Symbol.hasInstance属性，指向一个内部方法。当其他对象使用instanceof运算符，
判断是否为该对象的实例时，会调用这个方法。
比如，foo instanceof Foo在语言内部，实际调用的是Foo[Symbol.hasInstance](foo)。
*/
console.log('Symbol.hasInstance --------')
class MyClass {
    [Symbol.hasInstance](foo) {
        return foo instanceof Array;
    }
}

console.log([1, 2, 3] instanceof new MyClass()) // true
//上面代码中，MyClass是一个类，new MyClass()会返回一个实例。该实例的Symbol.hasInstance方法，
// 会在进行instanceof运算时自动调用，判断左侧的运算子是否为Array的实例。

/*
        Symbol.iterator 
对象的Symbol.iterator属性，指向该对象的默认遍历器方法。
*/
const myIterable = {};
myIterable[Symbol.iterator] = function* () {
    yield 1;
    yield 2;
    yield 3;
};

console.log([...myIterable])// [1, 2, 3]
// 对象进行for...of循环时，会调用Symbol.iterator方法